#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "hpcr_types.h"
#include "core/hpcr_map.h"
#include "hpcr_log.h"

static char *MapStringify(Map *map, char *buffer);
static bool MapKeyJudge(char *key);
static hpcr_uint32_t MapKeyHash(char *key);
static void InitMapEntry(MapEntry *mapEntry);
static void MapValueArrayFree(MapArray *mapArray);
static void MapValueFree(MapValue *mapValue);
static void MapIndexItemFree(MapIndexItem *mapIndexItem);
static void MapEntryFree(MapEntry *mapEntry);
static void MapEntryTransform(MapEntry *mapEntry);
static MapIndexItem *MapIndexItemInit();
static void MapAddMapIndexItemToEntry(MapEntry *mapEntry, MapIndexItem *mapIndexItem);
static void MapAddMapItemToEntry(MapEntry *mapEntry, MapItem *mapItem);
static void MapResize(Map *map);

static char mapStringifyBuffer[10240];
/*
 * Like Java HashMap. Initial CAPACITY is 16.
 * And PAYLOAD is 0.75.
 * When the number of map items are over than
 * the CAPACITY * PAYLOAD,
 * the map will resize the CAPACITY to 2 * CAPACITY.
 * When the size of a hash slot is over than 8,
 * the data struct of hash slot will become
 * BINARY TREE from LINK LIST.
 */
static bool MapKeyJudge(char *key)
{
    return strnlen(key, MAP_KEY_LENGTH) == MAP_KEY_LENGTH ? false : true;
}

static hpcr_uint32_t MapKeyHash(char *key)
{
    const hpcr_uint32_t base = 129;
    hpcr_uint32_t len = strlen(key), num = 0;
    for (int i = 0; i < len; i++)
        num = num * base + (hpcr_uint32_t)key[i]; // 权值展开
    return num;
}

static void InitMapEntry(MapEntry *mapEntry)
{
    mapEntry->len = 0;
    mapEntry->indexItem = NULL;
    mapEntry->type = MAP_DATA_STRUCT_LINKLIST;
}

// free Map

static void MapValueArrayFree(MapArray *mapArray)
{
    switch (mapArray->type)
    {
    case MAP_VALUE_TYPE_LONG:
    case MAP_VALUE_TYPE_DOUBLE:
        free(mapArray->array);
        break;
    case MAP_VALUE_TYPE_STRING:
        for (int i = 0; i < mapArray->len; i++)
        {
            free(((char **)mapArray->array)[i]);
        }
        break;
    case MAP_VALUE_TYPE_ARRAY:
        for (int i = 0; i < mapArray->len; i++)
        {
            MapValueArrayFree(((MapArray **)mapArray->array)[i]);
        }
        break;
    case MAP_VALUE_TYPE_MAP:
        for (int i = 0; i < mapArray->len; i++)
        {
            MapFree(((Map **)mapArray->array)[i]);
        }
        break;
    case MAP_VALUE_TYPE_NUL:
        break;
    case MAP_VALUE_TYPE_INVALI:
        assert(false);
    }
    free(mapArray);
}

static void MapValueFree(MapValue *mapValue)
{
    switch (mapValue->type)
    {
    case MAP_VALUE_TYPE_LONG:
    case MAP_VALUE_TYPE_DOUBLE:
    case MAP_VALUE_TYPE_STRING:
        free(mapValue->value);
        break;
    case MAP_VALUE_TYPE_ARRAY:
        MapValueArrayFree((MapArray *)(mapValue->value));
        break;
    case MAP_VALUE_TYPE_MAP:
        MapFree((Map *)(mapValue->value));
        break;
    case MAP_VALUE_TYPE_NUL:
        break;
    case MAP_VALUE_TYPE_INVALI:
        assert(false);
    }
}

void MapItemFree(MapItem *mapItem)
{
    assert(mapItem != NULL);
    MapValueFree(mapItem->value);
    free(mapItem);
}

static void MapIndexItemFree(MapIndexItem *mapIndexItem)
{
    MapItemFree(mapIndexItem->item);
    free(mapIndexItem);
}

static void MapEntryFree(MapEntry *mapEntry)
{
    if (mapEntry->type == MAP_DATA_STRUCT_LINKLIST)
    {
        MapIndexItem *now, *next;
        now = mapEntry->indexItem;
        while (now != NULL)
        {
            next = (MapIndexItem *)(now->index.linkList.next);
            MapIndexItemFree(now);
            now = next;
        }
    }
    else
    {
        assert(false);
    }
}

void MapFree(Map *map)
{
    int i = map->cap;
    while (i != 0)
    {
        i--;
        MapEntryFree(&(map->entrys[i]));
    }
    free(map->entrys);
    free(map);
}
/*
 * later deal
 */
static void MapEntryTransform(MapEntry *mapEntry)
{
    // transform to balence binary tree
}

static MapIndexItem *MapIndexItemInit()
{
    MapIndexItem *mapIndexItem =
        (MapIndexItem *)malloc(sizeof(MapIndexItem));
    mapIndexItem->index.binaryTree.left = NULL;
    mapIndexItem->index.binaryTree.right = NULL;
    return mapIndexItem;
}

static void MapAddMapIndexItemToEntry(MapEntry *mapEntry, MapIndexItem *mapIndexItem)
{
    mapEntry->len++;
    mapIndexItem->index.binaryTree.left = NULL;
    mapIndexItem->index.binaryTree.right = NULL;
    if (mapEntry->len > 8)
    {
        MapEntryTransform(mapEntry);
    }
    if (mapEntry->type == MAP_DATA_STRUCT_LINKLIST)
    {
        if (mapEntry->indexItem == NULL)
        {
            mapEntry->indexItem = mapIndexItem;
        }
        MapIndexItem *tmp = mapEntry->indexItem;
        while (tmp->index.linkList.next != NULL)
        {
            tmp = (MapIndexItem *)tmp->index.linkList.next;
        }
        tmp->index.linkList.next = mapIndexItem;
    }
    else
    {
        assert(false);
    }
}

static void MapAddMapItemToEntry(MapEntry *mapEntry, MapItem *mapItem)
{
    mapEntry->len++;
    if (mapEntry->len == 8)
    {
        MapEntryTransform(mapEntry);
    }
    if (mapEntry->type == MAP_DATA_STRUCT_LINKLIST)
    {
        if (mapEntry->indexItem == NULL)
        {
            MapIndexItem *mapIndexItem = MapIndexItemInit();
            mapIndexItem->item = mapItem;
            mapEntry->indexItem = mapIndexItem;
            return;
        }
        MapIndexItem *tmp = mapEntry->indexItem;
        while (tmp->index.linkList.next != NULL)
        {
            // 有重去重
            if (!strcmp(tmp->item->key, mapItem->key))
            {
                MapItemFree(tmp->item);
                tmp->item = mapItem;
                return;
            }
            tmp = (MapIndexItem *)tmp->index.linkList.next;
        }
        // 有重去重
        if (!strcmp(tmp->item->key, mapItem->key))
        {
            MapItemFree(tmp->item);
            tmp->item = mapItem;
            return;
        }
        MapIndexItem *mapIndexItem = MapIndexItemInit();
        mapIndexItem->item = mapItem;
        tmp->index.linkList.next = mapIndexItem;
    }
    else
    {
        assert(false);
    }
}

static void MapResize(Map *map)
{
    int oldCap = map->cap;
    map->cap *= 2;
    MapEntry *newMapEntrys =
        (MapEntry *)malloc(sizeof(MapEntry) * map->cap);
    for (int i = 0; i < map->cap; i++)
    {
        InitMapEntry(&(map->entrys[i]));
    }

    int newHashBase = map->cap - 1;
    MapIndexItem *mapIndexItem;
    MapIndexMeth *mapIndexMeth;

    // transport mapIndexItem from old newMapEntry to new MapEntry;
    for (int i = 0; i < oldCap; i++)
    {
        mapIndexItem = map->entrys[i].indexItem;
        if (map->entrys[i].type == MAP_DATA_STRUCT_LINKLIST)
        {
            while (mapIndexItem != NULL)
            {
                mapIndexMeth = &(mapIndexItem->index);
                MapAddMapIndexItemToEntry(&(newMapEntrys[mapIndexItem->item->hash & newHashBase]), mapIndexItem);
                mapIndexItem = (MapIndexItem *)mapIndexMeth->linkList.next;
            }
        }
        else
        {
            while (true)
            {
                assert(false);
            }
        }
    }
    free(map->entrys);
    map->entrys = newMapEntrys;
}

Map *MapInit()
{
    Map *map = (Map *)malloc(sizeof(Map));
    map->cap = MAP_DEFAULT_INITIAL_CAPACITY;
    map->len = 0;
    map->entrys = (MapEntry *)malloc(MAP_DEFAULT_INITIAL_CAPACITY * sizeof(MapEntry));
    for (int i = 0; i < MAP_DEFAULT_INITIAL_CAPACITY; i++)
    {
        InitMapEntry(map->entrys + i);
    }
    return map;
};

MapValue *MapValueInit(MapValueType type)
{
    MapValue *mapValue = (MapValue *)malloc(sizeof(MapValue));
    mapValue->type = type;
    mapValue->value = NULL;
    return mapValue;
}

MapItem *MapItemInit(char *key, MapValue *value)
{
    MapItem *mapItem = (MapItem *)malloc(sizeof(MapItem));
    mapItem->hash = MapKeyHash(key);
    assert(MapKeyJudge(key));
    strcpy(mapItem->key, key);
    mapItem->value = value;
    return mapItem;
}

MapArray *MapArrayInit()
{
    MapArray *mapArray = (MapArray *)malloc(sizeof(MapArray));
    mapArray->cap = 0;
    mapArray->len = 0;
    return mapArray;
}

bool MapPut(Map *map, MapItem *mapItem)
{
    assert(MapKeyJudge(mapItem->key));
    if (map->cap * MAP_PAYLOAD < map->len)
    {
        MapResize(map);
    }
    int index = mapItem->hash % map->cap;
    MapAddMapItemToEntry(&(map->entrys[index]), mapItem);
    map->len++;
    return true;
}

bool MapRemove(Map *map, char *key)
{
    assert(MapKeyJudge(key));
    hpcr_uint32_t hash = MapKeyHash(key);
    int index = hash % map->cap;
    MapEntry *mapEntry = &(map->entrys[index]);
    if (mapEntry->type == MAP_DATA_STRUCT_LINKLIST)
    {
        MapIndexItem *pre, *now;
        pre = NULL;
        now = mapEntry->indexItem;
        while (now != NULL && strncmp(key, now->item->key, MAP_KEY_LENGTH))
        {
            pre = now;
            now = (MapIndexItem *)now->index.linkList.next;
        }
        if (now == NULL)
        {
            return false;
        }
        else
        {
            if (pre == NULL)
            {
                mapEntry->indexItem = (MapIndexItem *)now->index.linkList.next;
            }
            else
            {
                pre->index.linkList.next = now->index.linkList.next;
            }
            MapIndexItemFree(now);
        }
    }
    else
    {
        assert(false);
    }
    return true;
}

static void MapArrayStringify(MapArray *mapArray, char *buffer)
{
    int tmp = strlen(buffer);
    buffer[tmp] = '[';
    buffer[tmp + 1] = '\0';
    int len = mapArray->len;

    switch (mapArray->type)
    {
    case MAP_VALUE_TYPE_LONG:
        for (int i = 0; i < len; i++)
        {
            sprintf(buffer, "%s%lld,", buffer, (((hpcr_int64_t *)mapArray->array)[i]));
        }
    case MAP_VALUE_TYPE_DOUBLE:
        for (int i = 0; i < len; i++)
        {
            sprintf(buffer, "%s%.3lf,", buffer, (((double *)mapArray->array)[i]));
        }
        break;
    case MAP_VALUE_TYPE_STRING:
        for (int i = 0; i < len; i++)
        {
            sprintf(buffer, "%s\"%s\",", buffer, ((char **)mapArray->array)[i]);
        }
        break;
    case MAP_VALUE_TYPE_ARRAY:
        for (int i = 0; i < len; i++)
        {
            MapArrayStringify(((MapArray **)mapArray->array)[i], buffer);
            tmp = strlen(buffer);
            buffer[tmp] = ',';
            buffer[tmp + 1] = '\0';
        }
        break;
    case MAP_VALUE_TYPE_MAP:
        for (int i = 0; i < mapArray->len; i++)
        {
            MapStringify(((Map **)mapArray->array)[i], buffer);
            tmp = strlen(buffer);
            buffer[tmp] = ',';
            buffer[tmp + 1] = '\0';
        }
        break;
    case MAP_VALUE_TYPE_NUL:
        break;
    case MAP_VALUE_TYPE_INVALI:
        assert(false);
    }
    buffer[strlen(buffer) - 1] = ']';
}

static void MapEntryStringify(MapEntry *mapEntry, char *buffer)
{
    if (mapEntry->len == 0)
    {
        return;
    }
    MapIndexItem *mapIndexItem = mapEntry->indexItem;
    MapValue *mapValue;
    if (mapEntry->type == MAP_DATA_STRUCT_LINKLIST)
    {
        while (mapIndexItem != NULL)
        {
            mapValue = mapIndexItem->item->value;
            switch (mapValue->type)
            {
            case MAP_VALUE_TYPE_LONG:
                sprintf(buffer, "%s%s:%lld,",
                        buffer, mapIndexItem->item->key,
                        *((hpcr_int64_t *)mapValue->value));
                break;
            case MAP_VALUE_TYPE_DOUBLE:
                sprintf(buffer, "%s%s:%.3lf,",
                        buffer, mapIndexItem->item->key,
                        *((double *)mapValue->value));
                break;
            case MAP_VALUE_TYPE_NUL:
                sprintf(buffer, "%s%s:%s,",
                        buffer, mapIndexItem->item->key,
                        "null");
                break;
            case MAP_VALUE_TYPE_STRING:
                sprintf(buffer, "%s%s:\"%s\",",
                        buffer, mapIndexItem->item->key,
                        (char *)mapValue->value);
                break;
            case MAP_VALUE_TYPE_ARRAY:
                sprintf(buffer, "%s%s:",
                        buffer, mapIndexItem->item->key);
                MapArrayStringify((MapArray *)(mapValue->value), buffer);
                sprintf(buffer, "%s,", buffer);
                break;
            case MAP_VALUE_TYPE_MAP:
                sprintf(buffer, "%s%s:",
                        buffer, mapIndexItem->item->key);
                MapStringify((Map *)(mapValue->value), buffer);
                sprintf(buffer, "%s,", buffer);
                break;
            case MAP_VALUE_TYPE_INVALI:
                assert(false);
            }
            mapIndexItem = (MapIndexItem *)mapIndexItem->index.linkList.next;
        }
    }
    else
    {
        assert(false);
    }
}

static char *MapStringify(Map *map, char *buffer)
{
    char *objBuffer = buffer == NULL ? mapStringifyBuffer : buffer;
    objBuffer[0] = '\0';
    sprintf(objBuffer, "%s%s", objBuffer, "{");
    if (map->len == 0)
    {
        sprintf(objBuffer, "%s%s",
                objBuffer, "}");
        return objBuffer;
    }
    for (int i = 0; i < map->cap; i++)
    {
        MapEntryStringify(&(map->entrys[i]), objBuffer);
    }
    objBuffer[strlen(objBuffer) - 1] = '}';
    return objBuffer;
}

// map to string and the end char is \n
char *MapString(Map *map, char *buffer)
{
    int len;
    char *strPtr = MapStringify(map, buffer);
    len = strlen(strPtr);

    strPtr[len] = '\n';
    strPtr[len + 1] = 0;
    return strPtr;
}

MapItem *MapGet(Map *map, char *key)
{
    hpcr_uint32_t hashCode = MapKeyHash(key);
    hpcr_uint32_t mod = hashCode % map->cap;
    MapEntry *mapEntry = &(map->entrys[mod]);
    MapIndexItem *tmpIndexItem = mapEntry->indexItem;
    if (mapEntry->type == MAP_DATA_STRUCT_LINKLIST)
    {
        while (tmpIndexItem != NULL)
        {
            if (!strncmp(key, tmpIndexItem->item->key, MAP_KEY_LENGTH))
            {
                return tmpIndexItem->item;
            }
            tmpIndexItem = (MapIndexItem *)tmpIndexItem->index.linkList.next;
        }
        return NULL;
    }
    else
    {
        printf("pass");
    }
    return NULL;
}

MapItem *MapStringItemInit(char *key, char *data)
{
    int len = strlen(data) + 1;
    MapValue *mapValue = MapValueInit(MAP_VALUE_TYPE_STRING);
    mapValue->value = malloc(len);
    memcpy(mapValue->value, data, len);
    return MapItemInit(key, mapValue);
}

MapItem *MapNumberInit(char *key, hpcr_int64_t data)
{
    MapValue *mapValue = MapValueInit(MAP_VALUE_TYPE_LONG);
    mapValue->value = malloc(sizeof(hpcr_int64_t));
    *(hpcr_int64_t *)(mapValue->value) = data;
    return MapItemInit(key, mapValue);
}

MapItem *MapDoubleInit(char *key, double data)
{
    MapValue *mapValue = MapValueInit(MAP_VALUE_TYPE_DOUBLE);
    mapValue->value = malloc(sizeof(double));
    *(double *)(mapValue->value) = data;
    return MapItemInit(key, mapValue);
}